package com.pine.generator;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.builder.CustomFile;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.querys.MySqlQuery;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.baomidou.mybatisplus.generator.keywords.MySqlKeyWordsHandler;
import com.baomidou.mybatisplus.generator.query.SQLQuery;
import com.pine.generator.config.*;
import com.pine.generator.core.EnumConvertorConfig;
import com.pine.generator.core.EnumValueConfig;
import com.pine.generator.core.Field;
import com.pine.generator.core.StringUtil;
import lombok.SneakyThrows;

import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.pine.generator.core.Constant.FieldNameConstants.*;

/**
 * <p>
 * 代码生成执行器
 * </p>
 *
 * @author pine manage
 * @since 2024-11-08
 */
public class GenerateCodeExecutor {

    private final FreemarkerTemplateEngine templateEngine;

    public GenerateCodeExecutor() {
        this.templateEngine = new FreemarkerTemplateEngine();
    }

    public void execute(GeneratorCodeConfig config) {
        // 数据库配置
        DataSourceConfig dataSourceConfig = buildDataSourceConfig(config.getMysqlConfig());

        // 全局配置
        GlobalConfig globalConfig = buildGlobalConfig(config.getProjectStructConfig());

        // 包配置+文件生成路径配置
        PackageConfig packageConfig = buildPackageConfig(config.getProjectStructConfig());

        // 模版配置
        StrategyConfig strategyConfig = buildStrategyConfig(config);
        InjectionConfig injectionConfig = buildInjectionConfig(config);

        // 初始化配置
        ConfigBuilder configbuilder = new ConfigBuilder(packageConfig, dataSourceConfig, strategyConfig, null, globalConfig, injectionConfig);
        templateEngine.setConfigBuilder(configbuilder);
        templateEngine.init(configbuilder).batchOutput();
    }


    private PackageConfig buildPackageConfig(ProjectStructConfig projectStructConfig) {
        DaoPackageConfig daoPackageConfig = projectStructConfig.getDaoPackageConfig();

        return new PackageConfig.Builder()
                .parent(projectStructConfig.getParentPackage())
                .entity(String.format(daoPackageConfig.getEntity(), projectStructConfig.getModuleName()))
                .mapper(String.format(daoPackageConfig.getMapper(), projectStructConfig.getModuleName()))
                .xml(String.format("mapper.%s", projectStructConfig.getModuleName()))
                // 配置xml文件路径
                .pathInfo(Collections.singletonMap(OutputFile.xml, projectStructConfig.getDirectory() + daoPackageConfig.getMapperXml()))
                .build();
    }


    private DataSourceConfig buildDataSourceConfig(MysqlConfig mysqlConfig) {
        // 数据库配置
        return new DataSourceConfig.Builder(mysqlConfig.getUrl(), mysqlConfig.getUsername(), mysqlConfig.getPassword())
                .dbQuery(new MySqlQuery())
                .schema("mybatis-plus")
                .keyWordsHandler(new MySqlKeyWordsHandler())
                .databaseQueryClass(SQLQuery.class)
                .build();
    }

    private GlobalConfig buildGlobalConfig(ProjectStructConfig projectStructConfig) {
        // 全局配置
        GlobalConfig.Builder globalConfigBuilder = new GlobalConfig.Builder()
                .outputDir(projectStructConfig.getDirectory() + "src/main/java")
                .author(projectStructConfig.getAuthor())
                .dateType(DateType.TIME_PACK)
                .commentDate("yyyy-MM-dd");

        // 启用springdoc
        globalConfigBuilder.enableSpringdoc();
        return globalConfigBuilder.build();
    }

    private StrategyConfig buildStrategyConfig(GeneratorCodeConfig config) {
        // 模版位置对应的是mybatis-plus-generator-3.5.7.jar包中的模板
        return new StrategyConfig.Builder()
                .enableSkipView() // 禁用视图过滤
                .addInclude(config.getTableName()) // 指定表
                .addTablePrefix(config.getCustomConfig().getTablePrefixList()) // 增加过滤表前缀
                .entityBuilder()
                .superClass(config.getCustomConfig().getBaseEntityPackage() + "." + config.getCustomConfig().getBaseEntityName())
                .enableLombok() // 启用 Lombok
                .javaTemplate("/templates/entity.java") // 设置实体类模板
                .addSuperEntityColumns(config.getCustomConfig().getBaseEntityFieldList()) // 添加父类公共字段
                .disable() // 禁用实体
                .idType(IdType.AUTO)
                .mapperBuilder()
                .enableBaseColumnList()
                .enableBaseResultMap()
                .mapperTemplate("/templates/mapper.java") // 设置 Mapper 模板
                .disableMapper()
                .mapperXmlTemplate("/templates/mapper.xml")
//                .disableMapperXml()
                .serviceBuilder()
                .disableService() // 禁用service
                .disableServiceImpl() // 禁用serviceImpl
                .controllerBuilder()
                .disable() // 禁用controller
                .build();
    }

    private InjectionConfig buildInjectionConfig(GeneratorCodeConfig config) {
        // 自定义模版中使用的变量
        InjectionConfig.Builder injectionConfig = new InjectionConfig.Builder().beforeOutputFile(((tableInfo, objectMap) -> this.appendCustomObject(config, tableInfo, objectMap)));

        // 自定义模版
        List<CustomTemplateConfig> configList = config.getCustomConfig().getCustomTemplateConfigList();
        String moduleName = config.getProjectStructConfig().getModuleName();
        configList.parallelStream().forEach(customTemplateConfig -> {
            customTemplateConfig.setPackageName(String.format(customTemplateConfig.getPackageName(), moduleName));
        });

        for (CustomTemplateConfig templateConfig : configList) {
            injectionConfig.customFile(new CustomFile.Builder()
                    .fileName(templateConfig.getFileName())
                    .templatePath(templateConfig.getTemplatePath())
                    .packageName(templateConfig.getPackageName())
                    .enableFileOverride()
                    .build()
            );
        }

        return injectionConfig.build();
    }

    @SuppressWarnings("unchecked")
    private void appendCustomObject(GeneratorCodeConfig config, TableInfo tableInfo, Map<String, Object> objectMap) {
        ProjectStructConfig projectStructConfig = config.getProjectStructConfig();
        BeansPackageConfig beansPackageConfig = projectStructConfig.getBeansPackageConfig(  );
        ServicePackageConfig servicePackageConfig = projectStructConfig.getServicePackageConfig();
        ManagerPackageConfig managerPackageConfig = projectStructConfig.getManagerPackageConfig();
        DaoPackageConfig daoPackageConfig = projectStructConfig.getDaoPackageConfig();
        WebPackageConfig webPackageConfig = projectStructConfig.getWebPackageConfig();
        CustomConfig customConfig = config.getCustomConfig();

        tableInfo.setComment(tableInfo.getComment().replace("表", ""));
        objectMap.put("stringStr", "String");
        objectMap.put("listString", "List");
        objectMap.put("entityLowerCaseFirstOne", StringUtil.toLowerCaseFirstOne(objectMap.get("entity").toString()));
        // 主键类型
        objectMap.put("primaryKeyType", customConfig.getPrimaryKeyType());
        objectMap.put("primaryKeyNameUpperCaseFirstOne", StringUtil.toUpperCaseFirstOne(customConfig.getPrimaryKeyName()));
        objectMap.put("primaryKeyName", customConfig.getPrimaryKeyName());

        // 设置包名
        Map<String, String> packageMap = (Map<String, String>) objectMap.get("package");
        packageMap = new HashMap<>(packageMap);
        packageMap.put("Bo", packageMap.get("Parent") + "." + String.format(managerPackageConfig.getBo(), projectStructConfig.getModuleName()));
        packageMap.put("Query", packageMap.get("Parent") + "." + String.format(managerPackageConfig.getQuery(), projectStructConfig.getModuleName()));
        packageMap.put("Entity", packageMap.get("Parent") + "." + String.format(daoPackageConfig.getEntity(), projectStructConfig.getModuleName()));
        packageMap.put("Mapper", packageMap.get("Parent") + "." + String.format(daoPackageConfig.getMapper(), projectStructConfig.getModuleName()));
        packageMap.put("Enum", packageMap.get("Parent") + "." + String.format(daoPackageConfig.getConstants(), projectStructConfig.getModuleName()));
        packageMap.put("QueryRequest", packageMap.get("Parent") + "." + String.format(beansPackageConfig.getRequest(), projectStructConfig.getModuleName()));
        packageMap.put("QueryResponse", packageMap.get("Parent") + "." + String.format(beansPackageConfig.getResponse(), projectStructConfig.getModuleName()));
        packageMap.put("SaveOrUpdateRequest", packageMap.get("Parent") + "." + String.format(beansPackageConfig.getRequest(), projectStructConfig.getModuleName()));
        packageMap.put("Convertor", packageMap.get("Parent") + "." + String.format(managerPackageConfig.getConvertors(), projectStructConfig.getModuleName()));
        packageMap.put("Service", packageMap.get("Parent") + "." + String.format(servicePackageConfig.getService(), projectStructConfig.getModuleName()));
        packageMap.put("ServiceImpl", packageMap.get("Parent") + "." + String.format(servicePackageConfig.getServiceImpl(), projectStructConfig.getModuleName()));
        packageMap.put("Manager", packageMap.get("Parent") + "." + String.format(managerPackageConfig.getManager(), projectStructConfig.getModuleName()));
        packageMap.put("Controller", packageMap.get("Parent") + "." + String.format(webPackageConfig.getController(), projectStructConfig.getModuleName()));
        objectMap.put("package", packageMap);

        // 设置基础类信息
        objectMap.put("baseEntityPackage", customConfig.getBaseEntityPackage());
        objectMap.put("baseEntityName", customConfig.getBaseEntityName());
        objectMap.put("baseMapperPackage", customConfig.getBaseMapperPackage());
        objectMap.put("baseMapperName", customConfig.getBaseMapperName());
        objectMap.put("baseQueryPackage", customConfig.getBaseQueryPackage());
        objectMap.put("baseQueryName", customConfig.getBaseQueryName());
        objectMap.put("baseManagerPackage", customConfig.getBaseManagerPackage());
        objectMap.put("baseManagerName", customConfig.getBaseManagerName());
        objectMap.put("pageRequestName", "PageRequest");
        objectMap.put("pageResponseName", "PageResponse");

        // 设置类名
        objectMap.put("BoName", tableInfo.getEntityName() + "Bo");
        objectMap.put("QueryName", tableInfo.getEntityName() + "Query");
        objectMap.put("EntityName", tableInfo.getEntityName());
        objectMap.put("QueryRequestName", tableInfo.getEntityName() + "QueryRequest");
        objectMap.put("QueryResponseName", tableInfo.getEntityName() + "QueryResponse");
        objectMap.put("SaveOrUpdateRequestName", tableInfo.getEntityName() + "SaveOrUpdateRequest");
        objectMap.put("ManagerName", tableInfo.getEntityName() + "Manager");
        objectMap.put("ConvertorName", tableInfo.getEntityName() + "Convertor");
        objectMap.put("ServiceName", tableInfo.getEntityName() + "Service");
        objectMap.put("ServiceImplName", tableInfo.getEntityName() + "ServiceImpl");
        objectMap.put("ControllerName", tableInfo.getEntityName() + "Controller");

        // 生成序列号
        List<String> serialVersionUids = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            serialVersionUids.add(String.valueOf(UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE));
        }
        objectMap.put("serialVersionUIDList", serialVersionUids);

        List<Field> fields = tableInfo.getFields().stream().map(field -> Field.buildByTableFieldWithEnumMap(field, config.getEnumNameMap())).collect(Collectors.toList());
        // 父级对象的字段：id, create_time, deleted
        List<Field> commonFields = tableInfo.getCommonFields().stream().map(field -> Field.buildByTableFieldWithEnumMap(field, config.getEnumNameMap())).toList();
        Map<String, Field> commonFieldsMap = commonFields.stream().collect(Collectors.toMap(Field::getColumnName, field -> field));
        // 全部字段
        List<Field> allFields = Stream.concat((Stream.concat(Stream.of(commonFieldsMap.get(ID)), fields.stream())), Stream.of(commonFieldsMap.get(CREATE_TIME), commonFieldsMap.get(DELETED))).toList();

        // entity字段
        objectMap.put("EntityFields", fields);
        objectMap.put("QueryFields", allFields.stream().filter(item -> !Arrays.asList(ID, DELETED, CREATE_USERNAME, CREATE_USER, CREATE_TIME, UPDATE_USER_ID, UPDATE_USERNAME, UPDATE_USER).contains(item.getName())).collect(Collectors.toList()));
        objectMap.put("QueryRequestFields", allFields.stream().filter(item -> !Arrays.asList(DELETED, CREATE_USERNAME, CREATE_USER, UPDATE_USERNAME, UPDATE_USER, UPDATE_USER_ID).contains(item.getName())).collect(Collectors.toList()));
        objectMap.put("QueryResponseFields", allFields.stream().filter(item -> !Objects.equals(DELETED, item.getName())).collect(Collectors.toList()));
        objectMap.put("SaveOrUpdateRequestFields", allFields.stream().filter(item -> !Arrays.asList(DELETED, CREATE_USER_ID, CREATE_USERNAME, CREATE_USER, CREATE_TIME, UPDATE_USER_ID, UPDATE_USERNAME, UPDATE_USER, UPDATE_TIME).contains(item.getName())).collect(Collectors.toList()));

        // 枚举
        List<Field> enumFields = fields.stream().filter(Field::isEnum).toList();
        generateEnum(enumFields, config, objectMap);
    }

    @SneakyThrows
    @SuppressWarnings("all")
    private void generateEnum(List<Field> enumFields, GeneratorCodeConfig config, Map<String, Object> objectMap) {
        List<String> enumPackages = new ArrayList<>();
        List<EnumConvertorConfig> enumConvertorConfigs = new ArrayList<>();
        objectMap.put("enumImportPackages", enumPackages);
        objectMap.put("enumConvertorInfos", enumConvertorConfigs);

        for (Field enumField : enumFields) {
            String comment = enumField.getComment();
            Map<String, String> enumNameMap = config.getEnumNameMap();
            String enumClassName = enumNameMap.get(enumField.getPropertyName());
            objectMap.put("EnumName", enumClassName);
            objectMap.put("lowerFirstCharEnumName", StringUtil.toLowerCaseFirstOne(enumClassName));
            parseEnumComment(enumField, comment, objectMap);

            Map<String, String> packageMap = (Map<String, String>) objectMap.get("package");
            String filePath = packageMap.get("Enum").replaceAll("\\.", "/");
            String fileName = config.getProjectStructConfig().getDirectory() + "src/main/java/" + filePath + "/" + enumClassName + ".java";
            enumPackages.add(packageMap.get("Enum") + "." + enumClassName);
            EnumConvertorConfig convertorConfig = new EnumConvertorConfig();
            convertorConfig.setEnumClassName(enumClassName);
            convertorConfig.setLowerFirstCharEnumName(String.valueOf(objectMap.get("lowerFirstCharEnumName")));
            convertorConfig.setEnumCodeType(String.valueOf(objectMap.get("enumCodeType")));
            convertorConfig.setLowerFirstCharEnumCodeType(String.valueOf(objectMap.get("lowerFirstCharEnumCodeType")));
            enumConvertorConfigs.add(convertorConfig);
            File file = new File(fileName);
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            file.createNewFile();
            templateEngine.writer(objectMap, "/mybatis-templates/Enum.java.ftl", file);
        }
    }


    private void parseEnumComment(Field enumField, String comment, Map<String, Object> objectMap) {
        EnumValueConfig enumValueConfig = new EnumValueConfig(enumField, comment);
        objectMap.put("enumCodeAndDescList", enumValueConfig.getEnumValues());
        objectMap.put("enumChineseName", enumValueConfig.getEnumChineseName());
        objectMap.put("enumCodeType", enumValueConfig.getCodeType());
        objectMap.put("lowerFirstCharEnumCodeType", enumValueConfig.getLowerFirstCharCodeType());
    }
}
